﻿using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Web;
using System.Windows;
using System.Windows.Markup;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using bATR.Config;
using ImageManipulation;


namespace bATR.Engines
{

	/// <summary>
	/// The bATR text engine itself! Let's generate some text images
	/// </summary>
	public class TextImageEngine
	{
		private bATRConfig _bATRConfig;
	
		/// <summary>
		/// Init
		/// </summary>
		public TextImageEngine()
		{
			bATRConfigLogic bcl = new bATRConfigLogic();
			_bATRConfig = bcl.Config;
		}


		/// <summary>
		/// Generates an bitmap in the supplied style and with the supplied text and max width
		/// </summary>
		/// <param name="fontStyle">style for the text</param>
		/// <param name="text">what text should written</param>
		/// <param name="maxWidth">the max width (0 if you don't care)</param>
		/// <returns>an bitmap presenting your text</returns>
		public System.Drawing.Bitmap GenerateTextImage(bATRStyle fontStyle, string text, int maxWidth)
		{
			// Define new type face
			Typeface typeFace = new Typeface(new FontFamily(_bATRConfig.FontPath + fontStyle.FontName)
				, fontStyle.FontStyle
				, fontStyle.FontWeight
				, fontStyle.FontStretch);

			CheckHasFont(typeFace, fontStyle);
				
			// Create formatted text
			FormattedText formattedText = this.CreateFormattedText(text, typeFace, fontStyle);// new FormattedText(text, new CultureInfo("en-us"), FlowDirection.LeftToRight, typeFace, fontStyle.FontSize, new SolidColorBrush(fontStyle.FontColor));

			if (maxWidth > 0) //max width set in usercontrol
				formattedText.MaxTextWidth = maxWidth;
			else if (fontStyle.RenderedImageWidth > 0) // maxwidth defined in style
				formattedText.MaxTextWidth = fontStyle.RenderedImageWidth;

			// Draw the text and return
			return DrawText(fontStyle, formattedText, new SolidColorBrush(fontStyle.BackgroundColor), typeFace);
		}


		/// <summary>
		/// Creates the bitmap and writes the supplied text on it
		/// </summary>
		/// <param name="fontStyle">style, in this context used for positioning the text and the optional inline image</param>
		/// <param name="textFormat">the text to be added</param>
		/// <param name="backgroundColor">background color to render the text on, the image itself is made transparent</param>
		/// <param name="typeFace">the typeface to use</param>
		/// <returns>a bitmap with the text and inline image</returns>
		private System.Drawing.Bitmap DrawText(bATRStyle fontStyle, FormattedText textFormat, Brush backgroundColor, Typeface typeFace)
		{
			System.Drawing.Bitmap bitmap;

			DrawingVisual drawingVisual = new DrawingVisual();
			DrawingContext drawingContext = drawingVisual.RenderOpen();
			double width = textFormat.Width + fontStyle.TextStartPoint.X + 2;


			// Try to calculate new width
			if (fontStyle.CharacterSpacing > 0)
				width += ((textFormat.Text.Length - 1) * fontStyle.CharacterSpacing) + ((textFormat.Text.Length - textFormat.Text.Replace(" ", "").Length) * fontStyle.CharacterSpacingSpaceSize);
			

			// Draw backgroundcolor
			if (fontStyle.RenderedImageWidth == 0 && fontStyle.RenderedImageHeight == 0)
				drawingContext.DrawRectangle(backgroundColor, null, new Rect(0, 0, width, textFormat.Height + fontStyle.TextStartPoint.Y));
			else
				drawingContext.DrawRectangle(backgroundColor, null, new Rect(0, 0, fontStyle.RenderedImageWidth, fontStyle.RenderedImageHeight));


			// Add inline image to drawing
			if (fontStyle.InlineImage.Length > 0)
				this.AddImageToDrawing(ref drawingContext, fontStyle);
				

			// Because a characterspacing is set all characters are drawn letter by letter
			if (fontStyle.CharacterSpacing > 0)
				DrawCharacterByCharacter(ref drawingContext, fontStyle, typeFace, textFormat);
			else // Draw text at once
				drawingContext.DrawText(textFormat, fontStyle.TextStartPoint);
			
				drawingContext.Close();


			RenderTargetBitmap rtb;
			// The BitmapSource that is rendered with a Visual.
			if (fontStyle.RenderedImageWidth == 0 && fontStyle.RenderedImageHeight == 0)
				rtb = new RenderTargetBitmap((int)width, (int)(textFormat.Height + fontStyle.TextStartPoint.Y), 96, 96, PixelFormats.Pbgra32);
			else
				rtb = new RenderTargetBitmap(fontStyle.RenderedImageWidth, fontStyle.RenderedImageHeight, 96, 96, PixelFormats.Pbgra32);

			rtb.Render(drawingVisual);
			MemoryStream outStream = new MemoryStream();
			BitmapEncoder bmpEnc = new BmpBitmapEncoder();
			bmpEnc.Frames.Add(BitmapFrame.Create(rtb)); // creates exception in medium trust
			bmpEnc.Save(outStream);
			
			
			bitmap = new System.Drawing.Bitmap(outStream);
			// Make bitmap transparent
			bitmap.MakeTransparent(System.Drawing.Color.FromArgb((int)fontStyle.BackgroundColor.A, (int)fontStyle.BackgroundColor.R, (int)fontStyle.BackgroundColor.G, (int)fontStyle.BackgroundColor.B ));

			OctreeQuantizer quantizer = new OctreeQuantizer(255, 8);
			return quantizer.Quantize(bitmap);
		}


		/// <summary>
		/// Checks if the required font is there
		/// </summary>
		/// <param name="typeFace"></param>
		/// <param name="fontStyle"></param>
		/// <returns></returns>
		private bool CheckHasFont(Typeface typeFace, bATRStyle fontStyle)
		{
			LanguageSpecificStringDictionary lsd = typeFace.FontFamily.FamilyNames;
			foreach (KeyValuePair<XmlLanguage, string> kvp in lsd)
			{
				if (kvp.Value == fontStyle.FontName)
					return true;
			}
			

			string hasFonts = string.Empty;
			foreach (KeyValuePair<XmlLanguage, string> kvp in lsd)
				hasFonts += kvp.Value;

			throw new ApplicationException("Font " + fontStyle.FontName + " in " + _bATRConfig.FontPath + " not found, has :" + hasFonts);
		}


		/// <summary>
		/// Draws the supplied text character by character
		/// </summary>
		/// <param name="drawingContext">context to draw on</param>
		/// <param name="fontStyle">style for drawing</param>
		/// <param name="typeFace">font face</param>
		/// <param name="textFormat">formatted text</param>
		private void DrawCharacterByCharacter(ref DrawingContext drawingContext, bATRStyle fontStyle, Typeface typeFace, FormattedText textFormat)
		{
			double x = fontStyle.TextStartPoint.X;
			for (int i = 0; i < textFormat.Text.Length; i++)
			{
				FormattedText ft = this.CreateFormattedText(textFormat.Text.Substring(i, 1), typeFace, fontStyle);
				drawingContext.DrawText(ft, new Point(x, fontStyle.TextStartPoint.Y));
				x += ft.Width;

				if (i < textFormat.Text.Length)
				{
					if (textFormat.Text.Substring(i, 1) == " ")
						x += fontStyle.CharacterSpacingSpaceSize;
					else
						x += fontStyle.CharacterSpacing;
				}
			}
		}


		/// <summary>
		/// Draws an image on the drawingcontext
		/// </summary>
		/// <param name="drawingContext"></param>
		/// <param name="fontStyle"></param>
		private void AddImageToDrawing(ref DrawingContext drawingContext, bATRStyle fontStyle)
		{
			if (!File.Exists(HttpContext.Current.Server.MapPath(fontStyle.InlineImage)))
				throw new ApplicationException("inline image not found [" + fontStyle.InlineImage + " -> " + HttpContext.Current.Server.MapPath(fontStyle.InlineImage) + "]");

			BitmapImage bi = new BitmapImage();
			bi.BeginInit();
			bi.UriSource = new Uri(HttpContext.Current.Server.MapPath(fontStyle.InlineImage));
			bi.DecodePixelWidth = (int)fontStyle.InlineImageRect.Width;
			bi.DecodePixelHeight = (int)fontStyle.InlineImageRect.Height;
			bi.EndInit();

			drawingContext.DrawImage(bi, fontStyle.InlineImageRect);
		}


		/// <summary>
		/// Creates a new formatted text by the supplied params
		/// </summary>
		/// <param name="textToDisplay"></param>
		/// <param name="typeFace"></param>
		/// <param name="fontStyle"></param>
		/// <returns></returns>
		private FormattedText CreateFormattedText(string textToDisplay, Typeface typeFace, bATRStyle fontStyle)
		{
			// Create formatted text
			FormattedText formattedText = new FormattedText(textToDisplay, new CultureInfo("en-us"), FlowDirection.LeftToRight, typeFace, fontStyle.FontSize, new SolidColorBrush(fontStyle.FontColor));

			// Add an underline or strike through
			if (fontStyle.HasTextDecorationPen)
			{
				TextDecorationCollection decos = new TextDecorationCollection();
				decos.Add(new TextDecoration(fontStyle.TextDecorationLocation, fontStyle.TextDecorationPen, fontStyle.TextDecorationPenOffset, TextDecorationUnit.Pixel, TextDecorationUnit.Pixel));
				formattedText.SetTextDecorations(decos);
			}

			// Add an optional gradient
			if (fontStyle.GradientAngle != null && fontStyle.GradientColor != null)
				formattedText.SetForegroundBrush(new LinearGradientBrush(fontStyle.FontColor, (Color)fontStyle.GradientColor, (double)fontStyle.GradientAngle));

			// Any lineheight defined, then set
			if (fontStyle.LineHeight > 0)
				formattedText.LineHeight = fontStyle.LineHeight;

			return formattedText;
		}


	}
}